package auth

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/golang-jwt/jwt/v4"
	"kratos-realword/internal/conf"
	"math"
	"strings"
	"time"
)

const (
	tokenConst = "Token"
)

type JwtObject struct {
	UserEmail string `json:"userEmail"`
	Nbf       time.Time
}

func GenerateToken(conf *conf.JWT, userEmail string) string {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"userEmail": userEmail,
		"nbf":       time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
	})
	// Sign and get the complete encoded token as a string using the secret
	tokenString, err := token.SignedString([]byte(conf.Secret))
	if err != nil {
		panic(err)
	}
	//spew.Dump(tokenString)
	return tokenString
}

func JwtAuth(conf *conf.JWT) middleware.Middleware {
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			if tr, ok := transport.FromServerContext(ctx); ok {
				tokenSplit := strings.SplitN(tr.RequestHeader().Get("Authorization"), " ", 2)
				if len(tokenSplit) != 2 || !strings.EqualFold(tokenSplit[0], tokenConst) {
					return nil, errors.New("Token Invalid")
				}
				tokenString := tokenSplit[1]
				var tokenInfo *jwt.Token
				//spew.Dump(tokenString)
				tokenInfo, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
					// Don't forget to validate the alg is what you expect:
					if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
						return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
					}
					// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
					return []byte(conf.Secret), nil
				})
				if err != nil {
					return nil, errors.New(err.Error())
				}
				//if claims, ok := tokenInfo.Claims.(jwt.MapClaims); ok && tokenInfo.Valid {
				//	userEmail := claims["userEmail"]
				//} else {
				//	return nil, errors.New("Token Invalid")
				//}
				if _, ok := tokenInfo.Claims.(jwt.MapClaims); !ok || !tokenInfo.Valid {
					return nil, errors.New("Token Invalid")
				}
				ctx = NewContext(ctx, tokenInfo.Claims.(jwt.MapClaims))
			}
			return handler(ctx, req)
		}
	}
}

type authKey struct{}

func NewContext(ctx context.Context, info jwt.Claims) context.Context {
	return context.WithValue(ctx, authKey{}, info)
}

func FromContext(ctx context.Context) JwtObject {
	if claims, ok := ctx.Value(authKey{}).(jwt.MapClaims); ok {
		//println(reflect.TypeOf(claims["userEmail"]).Name())
		//println(reflect.TypeOf(claims["nbf"]).Name())
		timestamp := claims["nbf"].(float64)
		tm := time.Unix(int64(timestamp), int64((timestamp-math.Floor(timestamp))*1e3)*1e6)
		return JwtObject{UserEmail: claims["userEmail"].(string), Nbf: tm}
	}
	return JwtObject{}
}
